/**
 * This file is part of aion-emu <aion-emu.com>.
 *
 *  aion-emu is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-emu is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-emu.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionemu.gameserver.model.gameobjects;

import java.util.List;
import java.util.Map;

import javolution.util.FastMap;

import org.apache.log4j.Logger;

import com.aionemu.commons.utils.Rnd;
import com.aionemu.gameserver.ai.AbstractAI;
import com.aionemu.gameserver.ai.CtrlEvent;
import com.aionemu.gameserver.ai.CtrlIntention;
import com.aionemu.gameserver.controllers.MovementController;
import com.aionemu.gameserver.controllers.ObserveController;
import com.aionemu.gameserver.controllers.attack.AggroList;
import com.aionemu.gameserver.controllers.attack.AttackResult;
import com.aionemu.gameserver.controllers.attack.AttackUtil;
import com.aionemu.gameserver.controllers.effect.EffectController;
import com.aionemu.gameserver.controllers.movement.MovementMask;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.geoEngine.GeoEngine;
import com.aionemu.gameserver.model.EmotionType;
import com.aionemu.gameserver.model.TaskId;
import com.aionemu.gameserver.model.TribeClass;
import com.aionemu.gameserver.model.gameobjects.interfaces.IReward;
import com.aionemu.gameserver.model.gameobjects.interfaces.ISummoned;
import com.aionemu.gameserver.model.gameobjects.knownList.KnownList;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.state.CreatureSeeState;
import com.aionemu.gameserver.model.gameobjects.state.CreatureState;
import com.aionemu.gameserver.model.gameobjects.stats.CreatureLifeStats;
import com.aionemu.gameserver.model.gameobjects.stats.StatEnum;
import com.aionemu.gameserver.model.templates.NpcWhichNotGatherableTemplate;
import com.aionemu.gameserver.model.templates.item.EAttackType;
import com.aionemu.gameserver.model.templates.spawn.SpawnTemplate;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS.TYPE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_EMOTION;
import com.aionemu.gameserver.network.aion.serverpackets.SM_MOVE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_NPC_INFO;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SKILL_CANCEL;
import com.aionemu.gameserver.skillengine.SkillEngine;
import com.aionemu.gameserver.skillengine.effect.EffectId;
import com.aionemu.gameserver.skillengine.model.HealType;
import com.aionemu.gameserver.skillengine.model.Skill;
import com.aionemu.gameserver.taskmanager.tasks.PacketBroadcaster;
import com.aionemu.gameserver.taskmanager.tasks.PacketBroadcaster.BroadcastMode;
import com.aionemu.gameserver.utils.MathUtil;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.utils.gametime.GameTimeManager;
import com.aionemu.gameserver.world.World;

/**
 * This class is representing movable objects, its base class for all in game objects that may move
 * 
 * @author -Nemesiss-, Lyahim
 * 
 */
public abstract class Creature extends NpcWhichNotGatherable
{
	private static final Logger						log			= Logger.getLogger(Creature.class);

	private CreatureLifeStats<? extends Creature>	lifeStats;
	private EffectController						effectController;
	private byte									seeState	= CreatureSeeState.NORMAL.getId();
	private Skill									castingSkill;
	private Map<Integer, Long>						skillCoolDowns;
	private int										transformedModelId;
	private ObserveController						observeController;
	private final AggroList							aggroList;
	private MoveData moveData;
	protected AbstractAI ai;

	/**
	 * 
	 * @param objId
	 * @param controller
	 * @param spawnTemplate
	 * @param objectTemplate
	 * @param position
	 */
	public Creature(int objId, SpawnTemplate spawnTemplate)
	{
		super(objId, spawnTemplate);
		if (spawnTemplate != null)
		this.objectTemplate = DataManager.NPC_DATA.getNpcTemplate(spawnTemplate.getTemplateId());
		
		setKnownlist(new KnownList(this));
		initializeAi();
		initializeMoveData();
		this.aggroList = new AggroList(this);
	}

	public static class MoveData
	{
		public int				moveStartTime;
		public int				moveTimestamp; // last update
		public float			xTarget;
		public float			yTarget;
		public float			zTarget;
		public byte				movementMask;
		public byte				heading;
		public float 			vehicleX;
		public float 			vehicleY;
		public float 			vehicleZ;
		public int 				vehicleSpeed;
	}

	protected void initializeMoveData()
	{
		moveData = new MoveData();
	}
	/**
	 * @return the lifeStats
	 */
	public CreatureLifeStats<? extends Creature> getLifeStats()
	{
		return lifeStats;
	}

	/**
	 * @param lifeStats the lifeStats to set
	 */
	public void setLifeStats(CreatureLifeStats<? extends Creature> lifeStats)
	{
		this.lifeStats = lifeStats;
	}

	public abstract void initializeAi();

	/**
	 * @return the effectController
	 */
	public EffectController getEffectController()
	{
		return effectController;
	}

	/**
	 * @param effectController the effectController to set
	 */
	public void setEffectController(EffectController effectController)
	{
		this.effectController = effectController;
	}

	/**
	 *  Is creature casting some skill
	 *  
	 * @return
	 */
	public boolean isCasting()
	{
		return castingSkill != null;
	}

	/**
	 *  Set current casting skill or null when skill ends
	 *  
	 * @param castingSkill
	 */
	public void setCasting(Skill castingSkill)
	{
		this.castingSkill = castingSkill;
	}

	/**
	 *  Current casting skill id
	 *  
	 * @return
	 */
	public int getCastingSkillId()
	{
		return castingSkill != null ? castingSkill.getSkillTemplate().getSkillId() : 0;
	}

	/**
	 *  Current casting skill
	 *  
	 * @return
	 */
	public Skill getCastingSkill()
	{
		return castingSkill;
	}

	/**
	 * All abnormal effects are checked that disable movements
	 * 
	 * @return
	 */
	public boolean canPerformMove()
	{
		return !(getEffectController().isAbnormalState(EffectId.CANT_MOVE_STATE) || !isSpawned());
	}

	/**
	 * All abnormal effects are checked that disable attack
	 * @return
	 */
	public boolean canAttack()
	{
		return !(getEffectController().isAbnormalState(EffectId.CANT_ATTACK_STATE) || isCasting() || isInState(CreatureState.RESTING) || isInState(CreatureState.PRIVATE_SHOP));
	}

	/**
	 * All abnormal effects are checked that disable skill use
	 * @return
	 */
	public boolean isAllSkillsDisabled()
	{
		return (getEffectController().isAbnormalState(EffectId.CANT_SKILL_USE_STATE) || isCasting() || isInState(CreatureState.RESTING) || isInState(CreatureState.PRIVATE_SHOP));
	}

	/**
	 * @return seeState
	 */
	public byte getSeeState()
	{
		return seeState;
	}

	/**
	 * @param seeState the seeState to set
	 */
	public void setSeeState(CreatureSeeState seeState)
	{
		this.seeState |= seeState.getId();
	}

	public void unsetSeeState(CreatureSeeState seeState)
	{
		this.seeState &= ~seeState.getId();
	}

	public boolean isInSeeState(CreatureSeeState seeState)
	{
		int isSeeState = this.seeState & seeState.getId();

		if (isSeeState == seeState.getId())
			return true;

		return false;
	}

	/**
	 * @return the transformedModelId
	 */
	public int getTransformedModelId()
	{
		return transformedModelId;
	}

	/**
	 * @param transformedModelId the transformedModelId to set
	 */
	public void setTransformedModelId(int transformedModelId)
	{
		this.transformedModelId = transformedModelId;
	}

	/**
	 * @return the aggroList
	 */
	public AggroList getAggroList()
	{
		return aggroList;
	}

	/**
	 * PacketBroadcasterMask
	 */
	private volatile byte	packetBroadcastMask;

	/**
	 * This is adding broadcast to player.
	 */
	public final void addPacketBroadcastMask(BroadcastMode mode)
	{
		packetBroadcastMask |= mode.mask();

		PacketBroadcaster.getInstance().add(this);

		// Debug
		if (log.isDebugEnabled())
			log.debug("PacketBroadcaster: Packet " + mode.name() + " added to creature " + getName());
	}

	/**
	 * This is removing broadcast from player.
	 */
	public final void removePacketBroadcastMask(BroadcastMode mode)
	{
		packetBroadcastMask &= ~mode.mask();

		// Debug
		if (log.isDebugEnabled())
			log.debug("PacketBroadcaster: Packet " + mode.name() + " removed from creature " + getName());
	}

	/**
	 * Broadcast getter.
	 */
	public final byte getPacketBroadcastMask()
	{
		return packetBroadcastMask;
	}

	/**
	 * @return the observeController
	 */
	public ObserveController getObserveController()
	{
		if (observeController == null)
			observeController = new ObserveController();
		return observeController;
	}

	public boolean isEnemy(Creature creature)
	{
		if (this instanceof ISummoned && ((ISummoned)this).getMaster() != null)
			return ((ISummoned)this).getMaster().isEnemy(creature);
		if (creature.getTribe() == this.getTribe())
			return false;
		if ((DataManager.TRIBE_RELATIONS_DATA.isAggressiveRelation(getTribe(), creature.getTribe()))
				|| (DataManager.TRIBE_RELATIONS_DATA.isHostileRelation(getTribe(), creature.getTribe())))
			return true;
		if (DataManager.TRIBE_RELATIONS_DATA.isFriendlyRelation(getTribe(), creature.getTribe())
				|| DataManager.TRIBE_RELATIONS_DATA.isSupportRelation(getTribe(), creature.getTribe()))
			return false;
		return true;
	}

	public TribeClass getTribe()
	{
		if (this instanceof ISummoned)
		{
			if (((ISummoned) this).getMaster() == null)
				return getObjectTemplate().getTribe();
			return ((ISummoned) this).getMaster().getTribe();
		}
		else
			return ((NpcWhichNotGatherableTemplate) objectTemplate).getTribe();
	}

	public boolean isAggroTo(Creature creature)
	{
		if (creature.getLevel() - 10 > getLevel())
			return false;
		else
			return DataManager.TRIBE_RELATIONS_DATA.isAggressiveRelation(getTribe(), creature.getTribe());
	}

	public boolean isHostileFrom(Creature npc)
	{
		return DataManager.TRIBE_RELATIONS_DATA.isHostileRelation(npc.getTribe(), getTribe());
	}

	public boolean isSupportTo(Creature npc)
	{
		return DataManager.TRIBE_RELATIONS_DATA.isSupportRelation(getTribe(), npc.getTribe());
	}

	/**
	 * 
	 * @param visibleObject
	 * @return
	 */
	public boolean canSee(Creature creature)
	{
		return creature.getVisualState() <= getSeeState();
	}

	/**
	 * 
	 * @param skillId
	 * @return
	 */
	public boolean isSkillDisabled(int skillId)
	{
		if (skillCoolDowns == null)
			return false;

		Long coolDown = skillCoolDowns.get(skillId);
		if (coolDown == null)
			return false;

		if (coolDown < System.currentTimeMillis())
		{
			skillCoolDowns.remove(skillId);
			return false;
		}

		return true;
	}

	/**
	 * 
	 * @param skillId
	 * @return
	 */
	public long getSkillCoolDown(int skillId)
	{
		if (skillCoolDowns == null || !skillCoolDowns.containsKey(skillId))
			return 0;

		return skillCoolDowns.get(skillId);
	}

	/**
	 * 
	 * @param skillId
	 * @param time
	 */
	public void setSkillCoolDown(int skillId, long time)
	{
		if (skillCoolDowns == null)
			skillCoolDowns = new FastMap<Integer, Long>().shared();

		skillCoolDowns.put(skillId, time);
	}

	/**
	 * @return the skillCoolDowns
	 */
	public Map<Integer, Long> getSkillCoolDowns()
	{
		return skillCoolDowns;
	}

	/**
	 * 
	 * @param skillId
	 */
	public void removeSkillCoolDown(int skillId)
	{
		if (skillCoolDowns == null)
			return;
		skillCoolDowns.remove(skillId);
	}

	@Override
	public void delete()
	{
		cancelAllTasks();
		setTarget(null);
		super.delete();
	}

	public void onAttack(Creature creature, int skillId, TYPE type, int damage)
	{
		this.onAttack(creature, skillId, type, damage, true);
	}

	/**
	 * Perform tasks when Creature was attacked //TODO may be pass only Skill object - but need to add properties in it
	 */
	public void onAttack(Creature creature, int skillId, TYPE type, int damage, boolean notifyAttackedObserver)
	{
		if (getLifeStats().isAlreadyDead())
			return;

		// Reduce the damage to exactly what is required to ensure death.
		// - Important that we don't include 7k worth of damage when the
		//   creature only has 100 hp remaining. (For AggroList dmg count.)
		if (damage > getLifeStats().getCurrentHp())
			damage = getLifeStats().getCurrentHp() + 1;

		Skill skill = getCastingSkill();
		if (skill != null && skill.getSkillTemplate().getCancelRate() > 0)
		{
			int cancelRate = skill.getSkillTemplate().getCancelRate();
			float conc = getGameStats().getStat(StatEnum.CONCENTRATION, 0).getCurrent() / 10;
			float maxHp = getGameStats().getMaxHP().getCurrent();
			float cancel = (cancelRate - conc) + ((damage) / maxHp * 50);
			if (Rnd.get(100) < cancel)
				cancelCurrentSkill();
		}
		if (notifyAttackedObserver)
			getObserveController().notifyAttackedObservers(creature);
		getAggroList().addDamage(creature, damage);
		getLifeStats().reduceHp(damage, creature);

		if (this instanceof Player)
			PacketSendUtility.broadcastPacket((Player) this, new SM_ATTACK_STATUS(this, type, skillId, damage), true);
		else
			PacketSendUtility.broadcastPacket(this, new SM_ATTACK_STATUS(this, type, skillId, damage));
	}

	/**
	 * Perform tasks when Creature was attacked
	 */
	public final void onAttack(Creature creature, final int damage)
	{
		getObserveController().notifyAttackObservers(creature);
		this.onAttack(creature, 0, TYPE.REGULAR, damage);
	}

	/** 
	* Cancel current skill and remove cooldown 
	*/
	public void cancelCurrentSkill()
	{
		Skill castingSkill = getCastingSkill();
		if (castingSkill != null)
		{
			if (hasAI())
				getAI().notifyEvent(CtrlEvent.EVT_FINISH_CASTING);
			removeSkillCoolDown(castingSkill.getSkillTemplate().getSkillId());
			setCasting(null);
			PacketSendUtility.broadcastPacketAndReceive(this, new SM_SKILL_CANCEL(this, castingSkill.getSkillTemplate().getSkillId()));
		}
	}

	/**
	 * 
	 * @param target
	 */
	public boolean attackTarget(final Creature target, int time)
	{
		/**
		 * Check all prerequisites
		 */
		if (target == null || !canAttack() || getLifeStats().isAlreadyDead() || !isSpawned() || !isEnemy(target))
		{
			log.debug("Dont attack target!!");
			return false;
		}

		/**
		 * Calculate and apply damage
		 */
		List<AttackResult> attackList;
		int attackType = 0;
		if (getAttackType() == EAttackType.PHYSICAL)
			attackList = AttackUtil.calculatePhysicalAttackResult(this, target);
		else
		{
			attackList = AttackUtil.calculateMagicalAttackResult(this, target);
			attackType = 1;
		}

		int damage = 0;
		for (AttackResult result : attackList)
		{
			damage += result.getDamage();
		}

		PacketSendUtility.broadcastPacketAndReceive(this, new SM_ATTACK(this, target, getGameStats().getAttackCounter(), time, attackType, attackList));
		getGameStats().increaseAttackCounter();
		final Creature creature = this;
		final int finalDmg = damage;
		if (time == 0)
		{
			target.onAttack(this, damage);
		}
		else
		{
			ThreadPoolManager.getInstance().schedule(new Runnable()
			{
				public void run()
				{
					target.onAttack(creature, finalDmg);
				}
			}, time);
		}
		return true;
	}

	public boolean onMoveTo(float targetX, float targetY, float targetZ)
	{
		byte movementMask = (byte)-32;
		boolean sendPacket = false;
		MoveData m = getMoveData();
		if (m.moveStartTime < 0 ||
				MathUtil.getDistance(m.xTarget, m.zTarget, m.yTarget, targetX, targetY, targetZ) > 0.2f)
		{
			sendPacket = true;
		}
		if (!isInState(CreatureState.FLYING) && !isInState(CreatureState.GLIDING) && !isInState(CreatureState.FLIGHT_TELEPORT))
			targetZ = GeoEngine.getInstance().getZ(getWorldId(), targetX, targetY, targetZ);
		onMoveTo(getX(), getY(), getZ(), targetX, targetY, targetZ, getHeading(), movementMask, sendPacket);
		return false;
	}
	
	public void onMoveTo(float x, float y, float z, float x2, float y2, float z2, byte heading, byte movementMask, boolean sendPacket)
	{
		onMoveTo(x, y, z, x2, y2, z2, heading, movementMask);
		if (sendPacket)
			PacketSendUtility.broadcastPacket(this, new SM_MOVE(this));
	}
	/**
	 * Perform tasks on Creature starting to move
	 */
	public void onMoveTo(float x, float y, float z, float x2, float y2, float z2, byte heading, byte movementMask)
	{
		if (!canPerformMove())
			return;
			
		getObserveController().notifyMoveObservers();
		if (moveData == null)
			moveData = new MoveData();

		moveData.heading = heading;
		moveData.xTarget = x2;
		moveData.yTarget = y2;
		moveData.zTarget = z2;
		moveData.movementMask = movementMask;
		moveData.moveStartTime = GameTimeManager.getGameTicks();
		moveData.moveTimestamp = moveData.moveStartTime;
		
		//TODO Vehicle movement......
		if ((movementMask & MovementMask.VEHICLE) == MovementMask.VEHICLE)
			return;
		MovementController.getInstance().add(this, moveData.moveStartTime);
	}

	/**
	 * Perform tasks on Creature stop move
	 */
	public void onStopMove(float x, float y, float z, byte heading)
	{
		this.getMoveData().movementMask = 0;
		this.getMoveData().moveStartTime = -1;
		MovementController.getInstance().remove(this);
		World.getInstance().updatePosition(this, x, y, z, heading);
		getObserveController().notifyMoveObservers();
		PacketSendUtility.broadcastPacket(this, new SM_MOVE(this));
	}

	/**
	 * Perform tasks on Creature stop move
	 */
	public void onStopMove()
	{
		onStopMove(getX(), getY(), getZ(), getHeading());
	}

	/**
	 * @param gameTicks
	 * @return
	 */
	public boolean updatePosition(int gameTicks)
	{
		MoveData m = moveData;

		// Check if the position has already been calculated
		if (m.moveTimestamp == gameTicks || m.moveStartTime == -1)
			return false;

		double dist = MathUtil.getDistance(getX(), getY(), getZ(), m.xTarget, m.yTarget, m.zTarget);
		float distX = (m.xTarget-getX());
		float distY = (m.yTarget-getY());
		float distZ = (m.zTarget-getZ());
		
		double distPassed = getGameStats().getMovementSpeed().getCurrent() * (gameTicks - m.moveTimestamp) / GameTimeManager.TICKS_PER_SECOND/1000f;
		
		double distFraction =  distPassed/dist;
		if (distFraction > 1)
		{
			World.getInstance().updatePosition(this, m.xTarget, m.yTarget, m.zTarget, m.heading, false);
			//TODO temp....
			this.onStopMove();
		}
		else
		{
			float x2 = (float) (distX*distFraction)+ getX();
			float y2 = (float) (distY*distFraction)+ getY();
			float z2 = (float) (distZ*distFraction)+ getZ();

			if (gameTicks % 10 == 0)
			{
				if (!isInState(CreatureState.FLYING) && !isInState(CreatureState.GLIDING) && !isInState(CreatureState.FLIGHT_TELEPORT))
					z2 = GeoEngine.getInstance().getZ(getWorldId(), x2, y2, z2);
			}
			World.getInstance().updatePosition(this, x2, y2, z2, m.heading, false);
		}
		// Set the timer of last position update to now
		m.moveTimestamp = gameTicks;
		return (distFraction > 1);
	}

	/**
	 * Perform tasks on Creature respawn
	 */
	@Override
	public void onRespawn()
	{
		super.onRespawn();
		unsetState(CreatureState.DEAD);
	}

	/* (non-Javadoc)
	 * @see com.aionemu.gameserver.model.gameobjects.VisibleObject#onDespawn()
	 */
	@Override
	public void onDespawn()
	{
		super.onDespawn();
		getAggroList().clear();
		if (hasAI())
			getAI().stopAI();
	}

	/**
	 * 
	 * @param hopType
	 * @param value
	 */
	public void onRestore(HealType hopType, int value)
	{
		switch (hopType)
		{
			case HP:
				getLifeStats().increaseHp(TYPE.HP, value);
				break;
			case MP:
				getLifeStats().increaseMp(TYPE.MP, value);
				break;
			case FP:
				getLifeStats().increaseFp(TYPE.FP, value);
				break;
		}
	}

	/**
	 * Die by reducing HP to 0
	 */
	public void die()
	{
		getLifeStats().reduceHp(getLifeStats().getCurrentHp() + 1, null);
	}

	/**
	 * 
	 * @param skillId
	 */
	public void useSkill(int skillId)
	{
		Skill skill = SkillEngine.getInstance().getSkill(this, skillId, 1, getTarget());
		if (skill != null)
		{
			skill.useSkill();
		}
	}

	/**
	 *  Notify hate value to all visible creatures
	 *  
	 * @param value
	 */
	public void broadcastHate(int value)
	{
		for (VisibleObject visibleObject : getKnownList().getKnownObjects().values())
		{
			if (visibleObject instanceof Creature)
			{
				((Creature) visibleObject).getAggroList().notifyHate(this, value);
			}
		}
	}

	public void abortCast()
	{
		Skill skill = getCastingSkill();
		if (skill == null)
			return;
		setCasting(null);
		this.cancelTask(TaskId.ACTION);
	}

	/**
	 * @param npcId
	 */
	public void createSummon(int npcId, int npcLevel)
	{
		// TODO Auto-generated method stub

	}

	/**
	 * Perform tasks on Creature death
	 */
	@Override
	public void onDie(Creature lastAttacker)
	{
		if (this instanceof IReward)
			((IReward) this).doReward();
		this.onStopMove();
		getAggroList().clear();
		this.setState(CreatureState.DEAD);
		this.unsetState(CreatureState.COMBAT);
		setTarget(null);
		PacketSendUtility.broadcastPacketAndReceive(this, new SM_EMOTION(this, EmotionType.DIE, 0, lastAttacker == null ? 0 : lastAttacker.getObjectId()));
		this.setCasting(null);
		this.getEffectController().removeAllEffects();
	}

	@Override
	public void see(VisibleObject object)
	{
		if (object instanceof Player)
			PacketSendUtility.sendPacket((Player) object, new SM_NPC_INFO(this, (Player) object));
		
		if (object instanceof Player && !((Player)object).isUsingFlyTeleport() && getAI() != null && getAI().getIntention() == CtrlIntention.IDLE)
			getAI().setIntention(CtrlIntention.ACTIVE);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void notSee(VisibleObject object, boolean isOutOfRange)
	{
		super.notSee(object, isOutOfRange);
		if (object == getTarget())
		{
			setTarget(null);
		}
		if (object instanceof Creature)
			getAggroList().remove((Creature) object);
		if (hasAI())
			getAI().notifyEvent(CtrlEvent.EVT_FORGET_OBJECT, object);
	}

	/**
	 * @return
	 */
	public AbstractAI getAI()
	{
		return ai;
	}

	/**
	 * @return
	 */
	public boolean hasAI()
	{
		return ai != null;
	}

	/**
	 * @return Returns the moveData.
	 */
	public MoveData getMoveData()
	{
		return moveData;
	}

	/**
	 * @param moveData The moveData to set.
	 */
	public void setMoveData(MoveData moveData)
	{
		this.moveData = moveData;
	}
	
	protected EAttackType getAttackType()
	{
		return EAttackType.PHYSICAL;
	}
}
